home *** CD-ROM | disk | FTP | other *** search
- #!/usr/bin/env python
-
- ## system-config-printer
-
- ## Copyright (C) 2006, 2007, 2008 Red Hat, Inc.
- ## Copyright (C) 2006 Florian Festi <ffesti@redhat.com>
- ## Copyright (C) 2006, 2007, 2008 Tim Waugh <twaugh@redhat.com>
-
- ## This program is free software; you can redistribute it and/or modify
- ## it under the terms of the GNU General Public License as published by
- ## the Free Software Foundation; either version 2 of the License, or
- ## (at your option) any later version.
-
- ## This program is distributed in the hope that it will be useful,
- ## but WITHOUT ANY WARRANTY; without even the implied warranty of
- ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- ## GNU General Public License for more details.
-
- ## You should have received a copy of the GNU General Public License
- ## along with this program; if not, write to the Free Software
- ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
- import cups
- from .cupshelpers import parseDeviceID
- import string
- import locale
- import os.path
- import re
- from . import _debugprint
-
- __all__ = ['ppdMakeModelSplit',
- 'PPDs']
-
- def ppdMakeModelSplit (ppd_make_and_model):
- """
- Split a ppd-make-and-model string into a canonical make and model pair.
-
- @type ppd_make_and_model: string
- @param ppd_make_and_model: IPP ppd-make-and-model attribute
- @return: a string pair representing the make and the model
- """
-
- # If the string starts with a known model name (like "LaserJet") assume
- # that the manufacturer name is missing and add the manufacturer name
- # corresponding to the model name
- if re.search ("^\s*(deskjet|dj\b|dj\d|laserjet|lj\b|color\s*laserjet|color\s*lj\b|designjet|officejet|oj\b|photosmart|ps\b|psc)", \
- ppd_make_and_model, re.I) or \
- re.search ("(edgeline)", ppd_make_and_model, re.I):
- make = "HP"
- model = ppd_make_and_model
- elif re.search ("^\s*(stylus|aculaser)", \
- ppd_make_and_model, re.I):
- make = "Epson"
- model = ppd_make_and_model
- elif re.search ("^\s*(stylewriter|imagewriter|deskwriter|laserwriter)", \
- ppd_make_and_model, re.I):
- make = "Apple"
- model = ppd_make_and_model
- elif re.search ("^\s*(pixus|pixma|selphy|imagerunner|\bbjc\b|\bbj\b|\blbp\b)",\
- ppd_make_and_model, re.I):
- make = "Canon"
- model = ppd_make_and_model
- elif re.search ("^\s*(\bhl\b|\bdcp\b|\bmfc\b)", \
- ppd_make_and_model, re.I):
- make = "Brother"
- model = ppd_make_and_model
- elif re.search ("^\s*(docuprint|docupage|phaser|workcentre|homecentre)", \
- ppd_make_and_model, re.I):
- make = "Xerox"
- model = ppd_make_and_model
- elif re.search ("^\s*(optra|(color\s*|)jetprinter)", \
- ppd_make_and_model, re.I):
- make = "Lexmark"
- model = ppd_make_and_model
- elif re.search ("^\s*(magicolor|pageworks|pagepro)", \
- ppd_make_and_model, re.I):
- make = "KONICA MINOLTA"
- model = ppd_make_and_model
- elif re.search ("^\s*(aficio)", \
- ppd_make_and_model, re.I):
- make = "Ricoh"
- model = ppd_make_and_model
- elif re.search ("^\s*(varioprint)", \
- ppd_make_and_model, re.I):
- make = "Oce"
- model = ppd_make_and_model
- elif re.search ("^\s*(okipage|microline)", \
- ppd_make_and_model, re.I):
- make = "Oki"
- model = ppd_make_and_model
- elif re.search ("^\s*(konica[\s_-]*minolta)", \
- ppd_make_and_model, re.I):
- make = "KONICA MINOLTA"
- model = ppd_make_and_model
- model = re.sub ("(?i)KONICA[\s_-]*MINOLTA\s*", "", model, 1)
- elif re.search("turboprint", ppd_make_and_model, re.I):
- # Support for Turboprint PPDs
- t = ppd_make_and_model.find (" TurboPrint")
- if t != -1:
- ppd_make_and_model = ppd_make_and_model[:t]
- try:
- make, model = ppd_make_and_model.split("_", 1)
- except:
- make = ppd_make_and_model
- model = ''
- make = re.sub (r"(?<=[a-z])(?=[0-9])", " ", make)
- make = re.sub (r"(?<=[a-z])(?=[A-Z])", " ", make)
- model = re.sub (r"(?<=[a-z])(?=[0-9])", " ", model)
- model = re.sub (r"(?<=[a-z])(?=[A-Z])", " ", model)
- model = re.sub (r" Jet", "Jet", model)
- model = re.sub (r"Photo Smart", "PhotoSmart", model)
- else:
- try:
- make, model = ppd_make_and_model.split(" ", 1)
- except:
- make = ppd_make_and_model
- model = ''
-
- def strip_suffix (model, suffix):
- if model.endswith (suffix):
- return model[:-len(suffix)]
- return model
-
- # Model names do not contain a comma, truncate all from the
- # comma on
- c = model.find (",")
- if c != -1:
- model = model[:c]
-
- # HP PPDs give NickNames like:
- # *NickName: "HP LaserJet 4 Plus v2013.111 Postscript (recommended)"
- # Find the version number.
- v = model.find (" v")
- if v != -1 and (model[v + 2].isdigit () or
- (model[v + 2] == '.' and
- model[v + 3].isdigit ())):
- # Truncate at that point.
- model = model[:v]
-
- h = model.find (" hpijs")
- if h != -1:
- model = model[:h]
-
- f = model.find (" Foomatic/")
- if f != -1:
- model = model[:f]
-
- # Gutenprint PPDs have NickNames that end:
- # ... - CUPS+Gutenprint v5.0.0
- gutenprint = model.find (" - CUPS+Gutenprint")
- if gutenprint != -1:
- model = model[:gutenprint]
-
- # Gimp-Print PPDs have NickNames that end:
- # ... - CUPS+Gimp-Print v4.2.7
- gimpprint = model.find (" - CUPS+Gimp-Print")
- if gimpprint != -1:
- model = model[:gimpprint]
-
- wth = model.find (" w/")
- if wth != -1:
- model = model[:wth]
-
- make = re.sub (r"(?i)KONICA[\s_-]*MINOLTA", "KONICA MINOLTA", make, 1)
- make = re.sub (r"(?i)HEWLETT[\s_-]PACKARD", "HP", make, 1)
- make = re.sub (r"(?i)Lexmark\s*International", "Lexmark", make, 1)
- model = re.sub (r"(?i)\s*\(recommended\)", "", model)
- model = re.sub (r"(?i)\s*-\s*PostScript\b", "", model)
- model = re.sub (r"(?i)\s*\bseries\b", "", model)
- if make.lower () == "hp":
- model = re.sub (r"(?i)^dj\b", "DeskJet", model)
- model = re.sub (r"(?i)^LJ\b", "LaserJet", model)
- model = re.sub (r"(?i)^OJ\b", "OfficeJet", model)
- model = re.sub (r"(?i)^Color\s*LJ\b", "Color LaserJet", model)
- model = re.sub (r"(?i)^PS\b", "PhotoSmart", model)
- model = re.sub (r"(?i)\s*\bPS[123]?\b", "", model)
- model = re.sub (r"(?i)\s*\bPXL", "", model)
- model = re.sub (r"(?i)[\s_-]+BT\b", "", model)
- model = re.sub (r"(?i)\s*\(Bluetooth\)", "", model)
- model = re.sub (r"(?i)\s*-\s*(RC|Ver(|sion))\s*-*\s*[0-9\.]+", "", model)
- model = re.sub (r"(?i)\s*-\s*(RC|Ver(|sion))\b", "", model)
- model = re.sub (r"(?i)\s*PostScript\s*$", "", model)
- model = re.sub (r"(?i)\s*\(\s*\)", "", model)
- model = re.sub (r"(?i)\s*[\-\/]\s*$", "", model)
-
- for mfr in [ "Apple", "Canon", "Epson", "Lexmark", "Oki" ]:
- if make == mfr.upper ():
- make = mfr
-
- model = model.strip ()
- return (make, model)
-
- # Some drivers are just generally better than others.
- # Here is the preference list:
- DRIVER_TYPE_DOWNLOADED_NOW = 5
- DRIVER_TYPE_FOOMATIC_RECOMMENDED_NON_POSTSCRIPT = 8
- DRIVER_TYPE_VENDOR = 10
- DRIVER_TYPE_FOOMATIC_RECOMMENDED_POSTSCRIPT = 15
- DRIVER_TYPE_FOOMATIC_HPIJS_ON_HP = 17
- DRIVER_TYPE_GUTENPRINT_NATIVE_SIMPLIFIED = 20
- DRIVER_TYPE_GUTENPRINT_NATIVE = 25
- DRIVER_TYPE_SPLIX = 27
- DRIVER_TYPE_FOOMATIC_PS = 30
- DRIVER_TYPE_FOOMATIC_HPIJS = 40
- DRIVER_TYPE_FOOMATIC_GUTENPRINT_SIMPLIFIED = 50
- DRIVER_TYPE_FOOMATIC_GUTENPRINT = 60
- DRIVER_TYPE_FOOMATIC = 70
- DRIVER_TYPE_CUPS = 80
- DRIVER_TYPE_FOOMATIC_GENERIC = 90
- DRIVER_TYPE_3RD_PARTY_NONFREE = 95
- DRIVER_DOES_NOT_WORK = 999
- def _getDriverType (ppdname, ppds=None):
- """Decides which of the above types ppdname is."""
- if ppdname.find ("turboprint") != -1:
- return DRIVER_TYPE_3RD_PARTY_NONFREE
- if ppdname.find ("gutenprint") != -1:
- if (ppdname.find ("/simple/") != -1 or
- ppdname.find (".sim-") != -1):
- return DRIVER_TYPE_GUTENPRINT_NATIVE_SIMPLIFIED
- else:
- return DRIVER_TYPE_GUTENPRINT_NATIVE
- if ppdname.find ("splix")!= -1:
- return DRIVER_TYPE_SPLIX
- if (ppdname.find (":") == -1 and
- ppdname.find ("/cups-included/") != -1):
- return DRIVER_TYPE_CUPS
- if ppdname.startswith ("foomatic:"):
- # Foomatic (generated) -- but which driver?
- if ppdname.find ("Generic")!= -1:
- return DRIVER_TYPE_FOOMATIC_GENERIC
- if (ppds != None and
- ppds.getInfoFromPPDName (ppdname).\
- get ('ppd-make-and-model', '').find ("(recommended)") != -1):
- if ppds.getInfoFromPPDName (ppdname).\
- get ('ppd-make-and-model', '').find ("Postscript") != -1:
- return DRIVER_TYPE_FOOMATIC_RECOMMENDED_POSTSCRIPT
- else:
- return DRIVER_TYPE_FOOMATIC_RECOMMENDED_NON_POSTSCRIPT
- if ppdname.find ("-Postscript")!= -1:
- return DRIVER_TYPE_FOOMATIC_PS
- if ppdname.find ("-hpijs") != -1:
- if ppdname.find ("hpijs-rss") == -1:
- return DRIVER_TYPE_FOOMATIC_HPIJS
- if ppdname.find ("-gutenprint") != -1:
- if ppdname.find ("-simplified")!= -1:
- return DRIVER_TYPE_FOOMATIC_GUTENPRINT_SIMPLIFIED
- return DRIVER_TYPE_FOOMATIC_GUTENPRINT
- return DRIVER_TYPE_FOOMATIC
- if ppdname.find ("-hpijs") != -1:
- if ppdname.find ("hpijs-rss") == -1:
- return DRIVER_TYPE_FOOMATIC_HPIJS
- # Anything else should be a vendor's PPD.
- return DRIVER_TYPE_VENDOR # vendor's own
-
-
- class PPDs:
- """
- This class is for handling the list of PPDs returned by CUPS. It
- indexes by PPD name and device ID, filters by natural language so
- that foreign-language PPDs are not included, and sorts by driver
- type. If an exactly-matching PPD is not available, it can
- substitute with a PPD for a similar model or for a generic driver.
- """
-
- # Status of match.
- STATUS_SUCCESS = 0
- STATUS_MODEL_MISMATCH = 1
- STATUS_GENERIC_DRIVER = 2
- STATUS_NO_DRIVER = 3
-
- def __init__ (self, ppds, language=None):
- """
- @type ppds: dict
- @param ppds: dict of PPDs as returned by cups.Connection.getPPDs()
-
- @type language: string
- @param language: language name, as given by the first element
- of the pair returned by locale.getlocale()
- """
- self.ppds = ppds.copy ()
- self.makes = None
- self.ids = None
-
- if (language == None or
- language == "C" or
- language == "POSIX"):
- language = "en_US"
-
- u = language.find ("_")
- if u != -1:
- short_language = language[:u]
- else:
- short_language = language
-
- to_remove = []
- for ppdname, ppddict in self.ppds.iteritems ():
- try:
- natural_language = ppddict['ppd-natural-language']
- except KeyError:
- continue
-
- if natural_language == "en":
- # Some manufacturer's PPDs are only available in this
- # language, so always let them though.
- continue
-
- if natural_language == language:
- continue
-
- if natural_language == short_language:
- continue
-
- to_remove.append (ppdname)
-
- for ppdname in to_remove:
- del self.ppds[ppdname]
-
- # CUPS sets the 'raw' model's ppd-make-and-model to 'Raw Queue'
- # which unfortunately then appears as manufacturer Raw and
- # model Queue. Use 'Generic' for this model.
- if self.ppds.has_key ('raw'):
- makemodel = self.ppds['raw']['ppd-make-and-model']
- if not makemodel.startswith ("Generic "):
- self.ppds['raw']['ppd-make-and-model'] = "Generic " + makemodel
-
- def getMakes (self):
- """
- @returns: a list of strings representing makes, sorted according
- to the current locale
- """
- self._init_makes ()
- makes_list = self.makes.keys ()
- makes_list.sort (locale.strcoll)
- try:
- # "Generic" should be listed first.
- makes_list.remove ("Generic")
- makes_list.insert (0, "Generic")
- except ValueError:
- pass
- return makes_list
-
- def getModels (self, make):
- """
- @returns: a list of strings representing models, sorted using
- cups.modelSort()
- """
- self._init_makes ()
- try:
- models_list = self.makes[make].keys ()
- except KeyError:
- return []
- models_list.sort (key=unicode.lower, cmp=cups.modelSort)
- return models_list
-
- def getInfoFromModel (self, make, model):
- """
- Obtain a list of PPDs that are suitable for use with a
- particular printer model, given its make and model name.
-
- @returns: a dict, indexed by ppd-name, of dicts representing
- PPDs (as given by cups.Connection.getPPDs)
- """
- self._init_makes ()
- try:
- return self.makes[make][model]
- except KeyError:
- return {}
-
- def getInfoFromPPDName (self, ppdname):
- """
- @returns: a dict representing a PPD, as given by
- cups.Connection.getPPDs
- """
- return self.ppds[ppdname]
-
- def orderPPDNamesByPreference (self, ppdnamelist=[],
- downloadedfiles=[]):
- """
-
- Sort a list of PPD names by (hard-coded) preferred driver
- type.
-
- @param ppdnamelist: PPD names
- @type ppdnamelist: string list
- @returns: string list
- """
- if len (ppdnamelist) < 1:
- return ppdnamelist
-
- dict = self.getInfoFromPPDName (ppdnamelist[0])
- make_model = dict['ppd-make-and-model']
- mfg, mdl = ppdMakeModelSplit (make_model)
- def getDriverTypeWithBias (x, mfg):
- t = _getDriverType (x, ppds=self)
- for file in downloadedfiles:
- (path, slash, filename) = file.rpartition ("/")
- (xpath, xslash, xfilename) = x.rpartition ("/")
- if filename == xfilename:
- return DRIVER_TYPE_DOWNLOADED_NOW
- if mfg == "HP":
- if t == DRIVER_TYPE_FOOMATIC_HPIJS:
- # Prefer HPIJS for HP devices.
- t = DRIVER_TYPE_FOOMATIC_HPIJS_ON_HP
- # For HP LaserJet 12xx/13xx prefer HPIJS over
- # PostScript, as they do not have enough memory
- # to render complex graphics with their on-board
- # PostScript interpreter
- if re.search(r"(?i)HP[-_]LaserJet_1[23]\d\d", x):
- t = DRIVER_TYPE_FOOMATIC_RECOMMENDED_NON_POSTSCRIPT
- return t
-
- def sort_ppdnames (a, b):
- ta = getDriverTypeWithBias (a, mfg)
- tb = getDriverTypeWithBias (b, mfg)
- if ta != tb:
- if tb < ta:
- return 1
- else:
- return -1
-
- # Prefer C locale localized PPDs to other languages, just
- # because we don't know the user's locale. This only
- # applies to PPDs provided by gutenprint 5.0; in 5.2 the
- # PPDs are localized properly.
- def is_C_locale (x):
- try:
- while x:
- i = x.find ("C")
- if i == -1:
- return False
- lword = False
- if i == 0:
- lword = True
- elif x[i - 1] not in string.letters:
- lword = True
-
- if lword:
- rword = False
- if i == (len (x) - 1):
- rword = True
- elif x[i + 1] not in string.letters:
- rword = True
- if rword:
- return True
-
- x = x[i + 1:]
- except UnicodeDecodeError:
- return False
-
- ca = is_C_locale (a)
- cb = is_C_locale (b)
- if ca != cb:
- # If they compare equal stringwise up to "C", sort.
- if ca:
- l = a.find ("C")
- else:
- l = b.find ("C")
-
- if a[:l] == b[:l]:
- if cb:
- return 1
- else:
- return -1
-
- # String-wise compare.
- if a > b:
- return 1
- elif a < b:
- return -1
- return 0
-
- ppdnamelist.sort (sort_ppdnames)
- return ppdnamelist
-
- def getPPDNameFromDeviceID (self, mfg, mdl, description="",
- commandsets=[], uri=None,
- downloadedfiles=[]):
- """
- Obtain a best-effort PPD match for an IEEE 1284 Device ID.
- The status is one of:
-
- - L{STATUS_SUCCESS}: the match was successful, and an exact
- match was found
-
- - L{STATUS_MODEL_MISMATCH}: a similar match was found, but
- the model name does not exactly match
-
- - L{STATUS_GENERIC_DRIVER}: no match was found, but a
- generic driver is available that can drive this device
- according to its command set list
-
- - L{STATUS_NO_DRIVER}: no match was found at all, and the
- returned PPD name is a last resort
-
- @param mfg: MFG or MANUFACTURER field
- @type mfg: string
- @param mdl: MDL or MODEL field
- @type mdl: string
- @param description: DES or DESCRIPTION field, optional
- @type description: string
- @param commandsets: CMD or COMMANDSET field, optional
- @type commandsets: string
- @param uri: device URI, optional (only needed for debugging)
- @type uri: string
- @returns: an integer,string pair of (status,ppd-name)
- """
- _debugprint ("\n%s %s" % (mfg, mdl))
- self._init_ids ()
- # Consider "HP" and "Hewlett-Packard" as equal, as the ID returned
- # by the CUPS "usb" backend and HPLIP's "hp" backend are different
- if mfg.lower () == "hewlett-packard": mfg = "HP"
- ppdnamelist = []
- id_matched = False
- try:
- ppdnamelist = self.ids[mfg.lower ()][mdl.lower ()]
- status = self.STATUS_SUCCESS
- id_matched = True
- except KeyError:
- pass
- # The HPLIP PPDs have incorrect IDs
- if mfg.lower () == "hp":
- try:
- ppdnamelist += self.ids['hp'][mdl.lower ()]
- status = self.STATUS_SUCCESS
- id_matched = True
- except KeyError:
- pass
- try:
- ppdnamelist += self.ids['hp'][mdl.lower ().replace (" ", "_")]
- status = self.STATUS_SUCCESS
- id_matched = True
- except KeyError:
- pass
- if mfg.lower () == "apollo":
- try:
- ppdnamelist += self.ids['apollo'][mdl.lower ()]
- status = self.STATUS_SUCCESS
- id_matched = True
- except KeyError:
- pass
- try:
- ppdnamelist += self.ids['apollo'][mdl.lower ().replace (" ", "_")]
- status = self.STATUS_SUCCESS
- id_matched = True
- except KeyError:
- pass
-
- _debugprint ("Trying make/model names")
- mfgl = mfg.lower ()
- mdls = None
- self._init_makes ()
- for attempt in range (2):
- for (make, models) in self.makes.iteritems ():
- if make.lower () == mfgl:
- mdls = models
- break
-
- # Try again with replacements.
- if mfgl == "hewlett-packard":
- mfgl = "hp"
- elif mfgl == "lexmark international":
- mfgl = "lexmark"
- elif mfgl == "":
- (tmp1, tmp2) = ppdMakeModelSplit (mdl)
- mfgl = tmp1.lower ()
-
- # Remove manufacturer name from model field
- ppdnamelist2 = None
- if mdl.startswith (mfg + ' '):
- mdl = mdl[len (mfg) + 1:]
- if mdl.startswith ('Hewlett-Packard '):
- mdl = mdl[16:]
- if mdl.startswith ('HP '):
- mdl = mdl[3:]
- if mdls:
- for (model, ppdnames) in mdls.iteritems ():
- if model.lower () == mdl.lower ():
- ppdnamelist2 = ppdnames.keys ()
- status = self.STATUS_SUCCESS
- break
- if not ppdnamelist2:
- # Make use of the model name clean-up in the
- # ppdMakeModelSplit () function
- (mfg2, mdl2) = ppdMakeModelSplit (mfg + " " + mdl)
- for (model, ppdnames) in mdls.iteritems ():
- if model.lower () == mdl2.lower ():
- ppdnamelist2 = ppdnames.keys ()
- status = self.STATUS_SUCCESS
- break
-
- if ppdnamelist:
- if ppdnamelist2:
- ppdnamelist = ppdnamelist + ppdnamelist2
- elif ppdnamelist2:
- ppdnamelist = ppdnamelist2
-
- if not ppdnamelist and mdls:
- (s, ppds) = self._findBestMatchPPDs (mdls, mdl)
- if s != self.STATUS_NO_DRIVER:
- status = s
- ppdnamelist = ppds
-
- if not ppdnamelist and commandsets:
- if type (commandsets) != list:
- commandsets = commandsets.split (',')
-
- generic = self._getPPDNameFromCommandSet (commandsets)
- if generic:
- status = self.STATUS_GENERIC_DRIVER
- ppdnamelist = generic
-
- if not ppdnamelist:
- _debugprint ("Text-only fallback")
- status = self.STATUS_NO_DRIVER
- ppdnamelist = ["textonly.ppd"]
- tppdfound = 0
- for ppdpath in self.ppds.keys ():
- if ppdpath.endswith (ppdnamelist[0]):
- tppdfound = 1
- ppdnamelist = [ppdpath]
- break
- if tppdfound == 0:
- _debugprint ("No text-only driver?! Using postscript.ppd")
- ppdnamelist = ["postscript.ppd"]
- psppdfound = 0
- for ppdpath in self.ppds.keys ():
- if ppdpath.endswith (ppdnamelist[0]):
- psppdfound = 1
- ppdnamelist = [ppdpath]
- break
- if psppdfound == 0:
- _debugprint ("No postscript.ppd; choosing any")
- ppdnamelist = [self.ppds.keys ()[0]]
-
- if id_matched:
- _debugprint ("Checking DES field")
- inexact = set()
- if description:
- for ppdname in ppdnamelist:
- if ppdname.find ("hpijs"): continue
- ppddict = self.ppds[ppdname]
- id = ppddict['ppd-device-id']
- if not id: continue
- # Fetch description field.
- id_dict = parseDeviceID (id)
- if id_dict["DES"] != description:
- inexact.add (ppdname)
-
- exact = set (ppdnamelist).difference (inexact)
- _debugprint ("discarding: %s" % inexact)
- if len (exact) >= 1:
- ppdnamelist = list (exact)
-
- # We've got a set of PPDs, any of which will drive the device.
- # Now we have to choose the "best" one. This is quite tricky
- # to decide, so let's sort them in order of preference and
- # take the first.
- ppdnamelist = self.orderPPDNamesByPreference (ppdnamelist,
- downloadedfiles)
- _debugprint ("Found PPDs: %s" % str (ppdnamelist))
-
- if not id_matched:
- sanitised_uri = re.sub (pattern="//[^@]*@/?", repl="//",
- string=str (uri))
- print "No ID match for device %s:" % sanitised_uri
- print " <manufacturer>%s</manufacturer>" % mfg
- print " <model>%s</model>" % mdl
- print " <description>%s</description>" % description
- try:
- cmd = reduce (lambda x, y: x + ","+ y, commandsets)
- except TypeError:
- cmd = ""
-
- print " <commandset>%s</commandset>" % cmd
- print "Using %s" % ppdnamelist[0]
-
- return (status, ppdnamelist[0])
-
- def _findBestMatchPPDs (self, mdls, mdl):
- """
- Find the best-matching PPDs based on the MDL Device ID.
- This function could be made a lot smarter.
- """
-
- _debugprint ("Trying best match")
- mdll = mdl.lower ()
- if mdll.endswith (" series"):
- # Strip " series" from the end of the MDL field.
- mdll = mdll[:-7]
- mdl = mdl[:-7]
- best_mdl = None
- best_matchlen = 0
- mdlnames = mdls.keys ()
-
- # Perform a case-insensitive model sort on the names.
- mdlnamesl = map (lambda x: (x, x.lower()), mdlnames)
- mdlnamesl.append ((mdl, mdll))
- mdlnamesl.sort (lambda x, y: cups.modelSort(x[1], y[1]))
- i = mdlnamesl.index ((mdl, mdll))
- candidates = [mdlnamesl[i - 1]]
- if i + 1 < len (mdlnamesl):
- candidates.append (mdlnamesl[i + 1])
- _debugprint (candidates[0][0] + " <= " + mdl + " <= " +
- candidates[1][0])
- else:
- _debugprint (candidates[0][0] + " <= " + mdl)
-
- # Look at the models immediately before and after ours in the
- # sorted list, and pick the one with the longest initial match.
- for (candidate, candidatel) in candidates:
- prefix = os.path.commonprefix ([candidatel, mdll])
- if len (prefix) > best_matchlen:
- best_mdl = mdls[candidate].keys ()
- best_matchlen = len (prefix)
- _debugprint ("%s: match length %d" % (candidate, best_matchlen))
-
- # Did we match more than half of the model name?
- if best_mdl and best_matchlen > (len (mdll) / 2):
- ppdnamelist = best_mdl
- if best_matchlen == len (mdll):
- status = self.STATUS_SUCCESS
- else:
- status = self.STATUS_MODEL_MISMATCH
- else:
- status = self.STATUS_NO_DRIVER
- ppdnamelist = None
-
- # Last resort. Find the "most important" word in the MDL
- # field and look for a match based solely on that. If
- # there are digits, try lowering the number of
- # significant figures.
- mdlnames.sort (cups.modelSort)
- mdlitems = map (lambda x: (x.lower (), mdls[x]), mdlnames)
- modelid = None
- for word in mdll.split (' '):
- if modelid == None:
- modelid = word
-
- have_digits = False
- for i in range (len (word)):
- if word[i].isdigit ():
- have_digits = True
- break
-
- if have_digits:
- modelid = word
- break
-
- digits = 0
- digits_start = -1
- digits_end = -1
- for i in range (len (modelid)):
- if modelid[i].isdigit ():
- if digits_start == -1:
- digits_start = i
- digits_end = i
- digits += 1
- elif digits_start != -1:
- break
- digits_end += 1
- modelnumber = 0
- if digits > 0:
- modelnumber = int (modelid[digits_start:digits_end])
- modelpattern = (modelid[:digits_start] + "%d" +
- modelid[digits_end:])
- _debugprint ("Searching for model ID '%s', '%s' %% %d" %
- (modelid, modelpattern, modelnumber))
- ignore_digits = 0
- best_mdl = None
- found = False
- while ignore_digits < digits:
- div = pow (10, ignore_digits)
- modelid = modelpattern % ((modelnumber / div) * div)
- _debugprint ("Ignoring %d of %d digits, trying %s" %
- (ignore_digits, digits, modelid))
-
- for (name, ppds) in mdlitems:
- for word in name.split (' '):
- if word.lower () == modelid:
- found = True
- break
-
- if found:
- best_mdl = ppds.keys ()
- break
-
- if found:
- break
-
- ignore_digits += 1
- if digits < 2:
- break
-
- if found:
- ppdnamelist = best_mdl
- status = self.STATUS_MODEL_MISMATCH
-
- return (status, ppdnamelist)
-
- def _getPPDNameFromCommandSet (self, commandsets=[]):
- """Return ppd-name list or None, given a list of strings representing
- the command sets supported."""
- try:
- self._init_makes ()
- models = self.makes["Generic"]
- except KeyError:
- return None
-
- def get (*candidates):
- for model in candidates:
- (s, ppds) = self._findBestMatchPPDs (models, model)
- if s == self.STATUS_SUCCESS:
- return ppds
- return None
-
- cmdsets = map (lambda x: x.lower (), commandsets)
- if (("postscript" in cmdsets) or ("postscript2" in cmdsets) or
- ("postscript level 2 emulation" in cmdsets)):
- return get ("PostScript Printer")
- elif (("pclxl" in cmdsets) or ("pcl-xl" in cmdsets) or
- ("pcl6" in cmdsets) or ("pcl 6 emulation" in cmdsets)):
- return get ("PCL 6/PCL XL Printer")
- elif "pcl5e" in cmdsets:
- return get ("PCL 5e Printer")
- elif "pcl5c" in cmdsets:
- return get ("PCL 5c Printer")
- elif ("pcl5" in cmdsets) or ("pcl 5 emulation" in cmdsets):
- return get ("PCL 5 Printer")
- elif "pcl" in cmdsets:
- return get ("PCL 3 Printer")
- elif (("escpl2" in cmdsets) or ("esc/p2" in cmdsets) or
- ("escp2e" in cmdsets)):
- return get ("ESC/P Dot Matrix Printer")
- return None
-
- def _init_makes (self):
- if self.makes:
- return
-
- makes = {}
- lmakes = {}
- lmodels = {}
- for ppdname, ppddict in self.ppds.iteritems ():
- ppd_make_and_model = ppddict['ppd-make-and-model']
- (make, model) = ppdMakeModelSplit (ppd_make_and_model)
- lmake = make.lower ()
- lmodel = model.lower ()
- if not lmakes.has_key (lmake):
- lmakes[lmake] = make
- lmodels[lmake] = {}
- makes[make] = {}
- else:
- make = lmakes[lmake]
-
- if not lmodels[lmake].has_key (lmodel):
- lmodels[lmake][lmodel] = model
- makes[make][model] = {}
- else:
- model = lmodels[lmake][lmodel]
-
- makes[make][model][ppdname] = ppddict
-
- self.makes = makes
- self.lmakes = lmakes
- self.lmodels = lmodels
-
- def _init_ids (self):
- if self.ids:
- return
-
- ids = {}
- for ppdname, ppddict in self.ppds.iteritems ():
- if not ppddict.has_key ('ppd-device-id'):
- continue
- id = ppddict['ppd-device-id']
- if not id:
- continue
-
- # Fix up broken Kyocera IDs
- v = id.find (":Model")
- if v != -1:
- id = id[:v] + ';' + id[v + 1:]
-
- id_dict = parseDeviceID (id)
- lmfg = id_dict['MFG'].lower ()
- lmdl = id_dict['MDL'].lower ()
-
- # Consider "HP" and "Hewlett-Packard" as equal, as the ID returned
- # by the CUPS "usb" backend and HPLIP's "hp" backend are different
- if lmfg == "hewlett-packard": lmfg = "hp"
-
- bad = False
- if len (lmfg) == 0:
- _debugprint ("Missing MFG field for %s" % ppdname)
- bad = True
- if len (lmdl) == 0:
- _debugprint ("Missing MDL field for %s" % ppdname)
- bad = True
- if bad:
- continue
-
- if not ids.has_key (lmfg):
- ids[lmfg] = {}
-
- if not ids[lmfg].has_key (lmdl):
- ids[lmfg][lmdl] = []
-
- ids[lmfg][lmdl].append (ppdname)
-
- self.ids = ids
-
- def _show_help():
- print "usage: ppds.py [--deviceid] [--list-models] [--list-ids] [--debug]"
-
- def _self_test(argv):
- import sys, getopt
- try:
- opts, args = getopt.gnu_getopt (argv[1:], '',
- ['help',
- 'deviceid',
- 'list-models',
- 'list-ids',
- 'debug'])
- except getopt.GetoptError:
- _show_help()
- sys.exit (1)
-
- stdin_deviceid = False
- list_models = False
- list_ids = False
-
- for opt, optarg in opts:
- if opt == "--help":
- show_help ()
- sys.exit (0)
- if opt == "--deviceid":
- stdin_deviceid = True
- elif opt == "--list-models":
- list_models = True
- elif opt == "--list-ids":
- list_ids = True
-
- picklefile="pickled-ppds"
- import pickle
- try:
- f = open (picklefile, "r")
- cupsppds = pickle.load (f)
- except IOError:
- f = open (picklefile, "w")
- c = cups.Connection ()
- cupsppds = c.getPPDs ()
- pickle.dump (cupsppds, f)
-
- ppds = PPDs (cupsppds)
- makes = ppds.getMakes ()
- models_count = 0
- for make in makes:
- models = ppds.getModels (make)
- models_count += len (models)
- if list_models:
- print make
- for model in models:
- print " " + model
- print "%d makes, %d models" % (len (makes), models_count)
- ppds.getPPDNameFromDeviceID ("HP", "PSC 2200 Series")
- makes = ppds.ids.keys ()
- models_count = 0
- for make in makes:
- models = ppds.ids[make]
- models_count += len (models)
- if list_ids:
- print make
- for model in models:
- print " %s (%d)" % (model, len (ppds.ids[make][model]))
- for driver in ppds.ids[make][model]:
- print " " + driver
- print "%d ID makes, %d ID models" % (len (makes), models_count)
-
- print "\nID matching tests\n"
-
- idlist = [
- # Format is:
- # (ID string, max status code, expected driver RE match)
-
- # Specific models
- ("MFG:EPSON;CMD:ESCPL2,BDC,D4,D4PX;MDL:Stylus D78;CLS:PRINTER;"
- "DES:EPSON Stylus D78;", 1, 'Epson Stylus D68'),
- ("MFG:Hewlett-Packard;MDL:LaserJet 1200 Series;"
- "CMD:MLC,PCL,POSTSCRIPT;CLS:PRINTER;", 0, 'HP LaserJet 1200'),
- ("MFG:Hewlett-Packard;MDL:LaserJet 3390 Series;"
- "CMD:MLC,PCL,POSTSCRIPT;CLS:PRINTER;", 0, 'HP LaserJet 3390'),
- ("MFG:Hewlett-Packard;MDL:PSC 2200 Series;CMD:MLC,PCL,PML,DW-PCL,DYN;"
- "CLS:PRINTER;1284.4DL:4d,4e,1;", 0, "HP PSC 2210"),
- ("MFG:HP;MDL:PSC 2200 Series;CLS:PRINTER;DES:PSC 2200 Series;",
- 1, "HP PSC 2210"),# from HPLIP
- ("MFG:HEWLETT-PACKARD;MDL:DESKJET 990C;CMD:MLC,PCL,PML;CLS:PRINTER;"
- "DES:Hewlett-Packard DeskJet 990C;", 0, "HP DeskJet 990C"),
- ("CLASS:PRINTER;MODEL:HP LaserJet 6MP;MANUFACTURER:Hewlett-Packard;"
- "DESCRIPTION:Hewlett-Packard LaserJet 6MP Printer;"
- "COMMAND SET:PJL,MLC,PCLXL,PCL,POSTSCRIPT;", 0, "HP LaserJet 6MP"),
- ("MFG:Canon;CMD:BJL,BJRaster3,BSCCe;SOJ:TXT01;MDL:iP3000;CLS:PRINTER;"
- "DES:Canon iP3000;VER:1.09;STA:10;FSI:03;", 1, "Canon PIXMA iP3000"),
- ("MFG:HP;MDL:Deskjet 5400 series;CMD:MLC,PCL,PML,DW-PCL,DESKJET,DYN;"
- "1284.4DL:4d,4e,1;CLS:PRINTER;DES:5440;", 1, "HP DeskJet 5440"),
- ("MFG:Hewlett-Packard;MDL:HP LaserJet 3390;"
- "CMD:PJL,MLC,PCL,POSTSCRIPT,PCLXL;",
- 0, "HP LaserJet 3390.*Postscript"),
-
- # Generic models
- ("MFG:New;MDL:Unknown PS Printer;CMD:POSTSCRIPT;",
- 2, "Generic postscript printer"),
- ("MFG:New;MDL:Unknown PCL6 Printer;CMD:PCLXL;", 2, "Generic PCL 6"),
- ("MFG:New;MDL:Unknown PCL5e Printer;CMD:PCL5e;", 2, "Generic PCL 5e"),
- ("MFG:New;MDL:Unknown PCL5c Printer;CMD:PCL5c;", 2, "Generic PCL 5c"),
- ("MFG:New;MDL:Unknown PCL5 Printer;CMD:PCL5;", 2, "Generic PCL 5"),
- ("MFG:New;MDL:Unknown PCL3 Printer;CMD:PCL;", 2, "Generic PCL"),
- ("MFG:New;MDL:Unknown ESC/P Printer;CMD:ESCP2E;", 2, "Generic ESC/P"),
- ("MFG:New;MDL:Unknown Printer;", 100, None),
- ]
-
- if stdin_deviceid:
- idlist = [(raw_input ('Device ID: '), 2, '')]
-
- all_passed = True
- for id, max_status_code, modelre in idlist:
- id_dict = parseDeviceID (id)
- (status, ppdname) = ppds.getPPDNameFromDeviceID (id_dict["MFG"],
- id_dict["MDL"],
- id_dict["DES"],
- id_dict["CMD"])
- if status < max_status_code:
- success = True
- elif status == max_status_code:
- ppddict = ppds.getInfoFromPPDName (ppdname)
- match = re.match (modelre, ppddict['ppd-make-and-model'], re.I)
- success = match != None
- else:
- success = False
-
- if success:
- result = "PASS"
- else:
- result = "*** FAIL ***"
-
- print "%s: %s %s" % (result, id_dict["MFG"], id_dict["MDL"])
- all_passed = all_passed and success
-
- if not all_passed:
- raise RuntimeError
-